/*****************************************************************************
 *  DOMINO (OpenDomo for Arduino) 
 *  Copyright (C) 2009  Oriol Palenzuela <opalenzuela@gmail.com>
 *  http://es.opendomo.org/domino
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/

#define VERSION "version 0.1.4"

#include <EEPROM.h>  
// Mapa de la EEPROM
#define EMPORTSOFFSET 0   //   0-199 Configuracion de los puertos (10B/puerto)
//#define EMPORTSOFFSET 200  200-300 Reservado para puertos virtuales y funciones 2.0 
#define EMLINKSOFFSET 400 // 400-499 Enlaces (max 50 enlaces)
#define EMBOARDOFFSET 500 // 500-512 La informacion de la placa
#define EMPORTSLOT 10     // Bytes reservados para cada puerto:
//      12345-6-7-8-9-0: nombre-config-cmin-wmin-wmax-cmax (analogicos)
//                       nombre-config-????-????-????-???? (digitales)


// Dependiendo del tipo de Arduino, vamos a disponer de un numero distinto de puertos ...
#define PINCOUNT  20  // (14 digitles + 6 analogicos) ...
#define DIGICOUNT 14  // ... de los cuales X son digitales
#define MAXLINKS  10  // Numero maximo de enlaces

#define ANALOG  65 //     ! ANALOG  DIGITAL
#define DIGITAL 68 // ----+----------------
#define IN      73 // IN  !   a       d
#define OUT     79 // OUT !   A       D
#define OFF      0 // Por defecto, los estados seran puestos a cero
#define ON      HIGH

// A falta de estructuras, usaremos algunas macros para facilitar el tratamiento
// de los puertos
#define portenabled(n)  (porttype[n]!=0)
#define FOREACHPORT(i)  for(i=0;i<PINCOUNT;i++) 

#define ISINPUT(n)   ((porttype[n]=='a')||(porttype[n]=='d'))
#define ISOUTPUT(n)  ((porttype[n]=='A')||(porttype[n]=='D'))
#define ISANALOG(n)  ((porttype[n]=='a')||(porttype[n]=='A'))
#define ISDIGITAL(n) ((porttype[n]=='d')||(porttype[n]=='D'))
#define ISPULSE(n)   ((porttype[n]=='P'))

#define BUFFERSIZE 20 // Longitud maxima permitida para el comando
#define MAXPORTCHANGESPERSECOND 5

// Estas funciones ayudaran en el tratamiento de cadenas
#define SPELL3(word,c1,c2,c3) ((word[0]==c1) && (word[1]==c2) && (word[2]==c3))
#define printstr(txt) Serial.print(txt)

// lnk puls1 alarm  
// 012345678901234
#define GETARG1(str,arg1) arg1[0]=str[4];arg1[1]=str[5];arg1[2]=str[6];arg1[3]=str[7];arg1[4]=str[8];arg1[5]=0;
#define GETARG2(str,arg2) arg2[0]=str[10];arg2[1]=str[11];arg2[2]=str[12];arg2[3]=str[13];arg2[4]=str[14];arg2[5]=0;
#define strcmp6(str1,str2) ((str1[0]==str2[0]) && (str1[1]==str2[1]) && (str1[2]==str2[2]) && (str1[3]==str2[3]) && (str1[4]==str2[4]) && (str1[5]==str2[5]))

#define ISVALIDCHAR(a) ((a>=48 && a<=57) || (a>=97 && a<=122))

// Arduino Duemilanove dispone de solo 1-2KB de SRAM. Optimizar estos valores: 
byte porttype[PINCOUNT];     // 20x1  20
byte portvalue[PINCOUNT];    // 20x1  20
byte portchanges[PINCOUNT];  // 20x1  20
char portname[PINCOUNT][6];  // 20x6 120
byte links[MAXLINKS][3];     // 30x3  90
//TOTAL                              270B

// Niveles de alerta en puertos analogicos de entrada (por defecto, inactivos)
//              a00  a01  a02  a03  a04  a05
int wmax[] = { 1024, 1024, 1024, 1024, 1024, 1024}; // Warning Max
int wmin[] = {    0,    0,    0,    0,    0,    0}; // Warning Min
int amax[] = { 1024, 1024, 1024, 1024, 1024, 1024}; // Alarm Max
int amin[] = {    0,    0,    0,    0,    0,    0}; // Alarm Min

char instruction[BUFFERSIZE];
byte inspos=0;
byte echo=0;
byte alarmport=0;
byte pulseport=0;
int loops=0;

// Registro global de tiempo
unsigned long time = 0;

//TODO Las funciones raiseEvent deberian ejecutar un solo Serial.println
// La funcion EVENT será usada para cualquier salida, que se compondrá con nivel, item y acción 
void raiseEvent(char *lvl, char *element, char *msg)
{
  Serial.print(lvl); 
  Serial.print(": "); 
  Serial.print(element); 
  Serial.print(" "); 
  Serial.println(msg);  
}
// Adaptacion de raiseEvent para indicar un parametro numerico extra
void raiseEvent4(char *lvl, char *element, char *msg, int val)
{
  Serial.print(lvl); 
  Serial.print(": "); 
  Serial.print(element); 
  Serial.print(" "); 
  Serial.print(msg); 
  Serial.print(" ");
  Serial.println(val,DEC); 
}

// Rutina de copia de cadenas
void strncpy(char *dest, int len, char *src)
{
  byte i;
  for (i=0;i<len;i++)
  {
    dest[i]=src[i];
  }
  dest[i]=0;
}

// Esta función convierte una cadena de hasta 5 bytes en un número entero
int atoi(char * str) 
{
  int i;
  int retval=0;
  for (i=0;i<5;i++)
  {
    if (str[i]>47 && str[i]<58)
    {
      retval = retval * 10 + (str[i]-48);
    }
  }
  return retval;
}

// Convierte un entero a una cadena de longitud LEN
void itoan(int val, char *result, int len)
{
  int i=val;
  int c=0;

  for (i=0;i<len;i++) result[i]='0'; //Rellenamos con ceros

  while(i>0)  
  {
    c++;
    i /= 10;
  }

  if(c>len) return; // Too less bytes to represent

  result[len]=0;
  c=len;
  i=val;
  while(i>0)
  {
    int digit = i - 10*(i/10);
    i /= 10;
    result[--c]=digit+48;
  }
}

// Esta función convierte un entero en una representación de 4 bytes
int printnum(int val)
{
  char ret[10];
  itoan(val,ret,4);
  Serial.print(ret);
}




// *************************************** PULSE TEST ***********************
#define PULSE_BIT_LENGTH 48
#define BIT_1 1000          //Binary 1 threshold (Microseconds)
#define BIT_0 400           //Binary 0 threshold (Microseconds)
#define BIT_START 2000      //Start bit threshold (Microseconds)


// setReceiving: Pone la variable a receiving a 1 cuando hay la interrupcion
void setReceiving(){
  getPulseHexCode();
  Serial.println();
}

void getPulseHexCode()
{
  unsigned int fpl=0; // First pulse lenght
  fpl = pulseIn(pulseport, HIGH, 1000000); //Maximum is 120ms, over Philips protocol
  if (fpl==0) return; // silent timeout
  
  int data[PULSE_BIT_LENGTH];
  byte bytes[5];
  byte b=0;
  int i;
  int irCode=0; // clear ir code
  int mask = 1; // set mask to bit 0

/*  while(pulseIn(pulseport, HIGH, 10000)< BIT_START)
  { 
      // Esperamos por el primer pulso
      Serial.print(".");
      i++;
      if (i>=100) return;
  } */
 
  // Solo si "cazamos" el primer pulso largo, seguiremos
  for (i = 0; i < PULSE_BIT_LENGTH; i++)
  {
    data[i] = pulseIn(pulseport, HIGH, 4000);
    if (data[i]<10000){ // Only under 10 milliseconds
      if (data[i]>1200) 
        irCode |= mask; // yes, update ir code
      mask <<= 1; // shift mask to next bit
      if (mask>256){
        bytes[b]=irCode;
        irCode=0;
        mask=1;
        b++;
      }
    } else { // timeout
 //      return;
      if (data[i]>9000){
         Serial.println("E: NEC protocol");
      }
    }
  }
  
  Serial.print("I: FIRST PULSE LENGHT ");
  Serial.println(fpl,DEC);
  
  Serial.print("N: ");
  Serial.print(portname[pulseport]);
  Serial.print(" (");
  for (i = 0; i < PULSE_BIT_LENGTH; i++)
  {
    if ((data[i])>1000) Serial.print("1");
    else if((data[i])>=500)  Serial.print("0");
    else if(data[i]<0) Serial.print("-");
    else Serial.print(" ");
//    Serial.print(data[i]/100,DEC);
//    Serial.print(" ");
  }
  
  Serial.print(")");
  for (i=0;i<=b;i++){
    Serial.print(bytes[i],DEC);
    Serial.print(" ");
  }
  Serial.println();
}
 // ***********************************************************************



// ResetPorts: Aplicamos la configuracion a los puertos
void resetPorts()
{
  int i;
  FOREACHPORT(i)
  {
    if(ISINPUT(i)) 
    {
      pinMode(i, INPUT);
    }
    else if(ISOUTPUT(i))
    {
      pinMode(i, OUTPUT);
    }
    else if(ISPULSE(i))
    {
      pinMode(i, INPUT);
      pulseport=i;
      //noInterrupts();
      if(i==2) attachInterrupt(0,setReceiving,FALLING);
      if(i==3) attachInterrupt(1,setReceiving,FALLING);
    }
  }

} 

// loadConfig: carga la configuracion desde la EEPROM
int loadConfig()
{
  int x;
  int y;

  FOREACHPORT(x)
  {
    for (y=0;y<5;y++)
    {
      portname[x][y]=EEPROM.read(x*EMPORTSLOT+y);
      // If the port name slots use different values than a-z, return error
      if (!ISVALIDCHAR(portname[x][y])) 
      {
        raiseEvent("E",portname[x],"invalid char found");
        return 1;
      }
    }
    portname[x][5]= 0; // Cerramos cadena limpiamente
    porttype[x]  = EEPROM.read(x*EMPORTSLOT+5);
  }
  for (x=0;x<MAXLINKS;x++)
  {
    links[x][0] = EEPROM.read(EMLINKSOFFSET+x*3)-65; //Unshift para convertirlo desde ASCII imprimible
    links[x][1] = EEPROM.read(EMLINKSOFFSET+x*3+1)-65;
    links[x][2] = EEPROM.read(EMLINKSOFFSET+x*3+2);
  }
  return 0;
}

// saveConfig: Guarda la configuracion en la EEPROM
int saveConfig()
{
  int x;
  int y;
  int dir;
  byte b;

  FOREACHPORT(x)
  {
    dir = EMPORTSOFFSET + x * EMPORTSLOT;
    for (y=0;y<5;y++)
    {
      b = portname[x][y];
      EEPROM.write(dir+y,b);
    }
    EEPROM.write(dir+5,porttype[x]);
  }
  for (x=0;x<MAXLINKS;x++)
  {
    EEPROM.write(EMLINKSOFFSET+x*3,links[x][0]+65); //Shift para convertirlo en ASCII imprimible
    EEPROM.write(EMLINKSOFFSET+x*3+1,links[x][1]+65);
    EEPROM.write(EMLINKSOFFSET+x*3+2,links[x][2]);
  }
  return 0;
}

// Esta funcion imprime por el serie el nombre de la placa. Si no existe, devuelve error.
//TODO: Allow longer board name (at least, 8 chars)
int printBoardName()
{
  int i;
  char name[5];
  for (i=0;i<5;i++) 
  {
    name[i] = EEPROM.read(EMBOARDOFFSET+i);
    if (name[i]<97 && name[i]>122)
    {
       saveBoardName("odard");
       Serial.println("I: board name odard");
       return 0;
    } 
  }
  name[5]=0;
  raiseEvent("I","board name",name);
  return 0;
}

// Esta funcion guarda el nombre indicado directamente en EEPROM
int saveBoardName(char *name)
{
  int i;
  for (i=0;i<5;i++) 
  {
    EEPROM.write(EMBOARDOFFSET+i,name[i]);
  }
  return 0;
}

// Esta rutina asigna la configuracion para un puerto
int configPort(int id, char *cfg, char *name)
{
  int i;
  if (id<0) return -1; // Ignoramos ID =-1 (puerto no encontrado)
  if (cfg!=NULL)
  {  
    /* Anulamos los rangos de alerta
    emax[id]=100;
    wmax[id]=100;
    emin[id]=0;
    wmin[id]=0;
    */
    porttype[id]='X';
    for (i=0;i<5;i++)
    {
      if (cfg[i]=='P'){
        pulseport=id;
        porttype[id]='P';
      }
      if (cfg[i]=='A') alarmport=id;
      if (cfg[i]=='d') porttype[id]='D';
      if (cfg[i]=='a') porttype[id]='A';
      if (cfg[i]=='b') porttype[id]='B';
      if (cfg[i]=='i')
      {
        if (porttype[id]=='D') porttype[id]='d';
        if (porttype[id]=='A') porttype[id]='a';
        if (porttype[id]=='B') porttype[id]='b';
      }
    }
  }

  if (name!=NULL)
    strncpy(portname[id],5,name);
    
  return 0;
}  

void loadDefaultConfig()
{  
   configPort( 0,"-",   "usbrx"); // RX del USB. Ocupado
   configPort( 1,"-",   "usbtx"); // TX del USB. Ocupado
   configPort( 2,"do",  "do002"); // (1)
   configPort( 3,"do",  "do003"); // (1) (2)
   configPort( 4,"do",  "do004"); 
   configPort( 5,"do",  "do005"); // (2)
   configPort( 6,"do",  "do006"); // (2)
   configPort( 7,"do",  "do007");  
   configPort( 8,"do",  "do008");
   configPort( 9,"do",  "do009"); // (2)
   configPort(10,"do",  "do010"); // (2)
   configPort(11,"do",  "do011"); // (2)
   configPort(12,"do",  "do012"); 
   configPort(13,"doA", "do013"); // (3)
   configPort(14,"-",   "ai000"); 
   configPort(15,"-",   "ai001"); 
   configPort(16,"-",   "ai002"); 
   configPort(17,"-",   "ai003");  
   configPort(18,"-",   "ai004"); 
   configPort(19,"-",   "ai005");
   // (1) Estos puertos pueden desencadenar una interrupcion prioritaria   
   // (2) Estos puertos pueden simular una salida analógica con PWM
   // (3) Puerto de alarma por defecto (dispone del LED en placa)
}


// Setup: Inicializacion del dispositivo. Se llama al conectar alimentacion  
void setup()
{
  int i;
  Serial.begin(9600);
  for (i=0;i<MAXLINKS;i++) 
  {
    links[i][0]=0;
    links[i][1]=0; 
    links[i][2]=0;
  }
  FOREACHPORT(i) portchanges[i]=0; // Ponemos a cero los contadores
  if(loadConfig()!=0)
  {
     loadDefaultConfig();   
  }
  // Iniciamos los puertos
  resetPorts();
}

// Loop: Iteracion permanente. Salir de este bucle implica parada logica
void loop()
{
  char b;
  byte i;
  // El dispositivo Arduino estara permanentemente iterando este bucle,
  // este o no conectado a traves del puerto USB. En el caso de que haya
  // conexion establecida, se tendra en cuenta.

  // Serial.available() devuelve el numero de bytes acumulados en el buffer. 
  // Empleando un sistema de ancho fijo de los comandos facilitaremos en 
  // gran medida el procesado   
  //noInterrupts();
  while (Serial.available()>0) 
  {
    b = Serial.read();
    switch (b) {
    case -1: // Null char
    case 0:  // Null char
    case 10: // LF: No deberia suceder, salvo en consola emulada 
      break;
    case 13: // Final de linea
      processInstruction(instruction);
      inspos=0;
      break;
    case 8:  // Backspace
      if (inspos>0) 
      {
        inspos--;
        if (echo==1) Serial.print(b);
      }
      instruction[inspos]=0;
      break;
    default:
      if (inspos < BUFFERSIZE) 
      {
        if (echo==1) Serial.print(b);
        instruction[inspos]=b;
        inspos++;
        instruction[inspos]=0;
      }
      else
      {
        raiseEvent("E","buffer", "full");
      }
    }
  }

  // Aparte de comprobar los comandos recibidos por Serial, miramos si 
  // hay algun cambio de estado en los puertos. 
  refreshPortStatus();

  // Activamos las interrupciones en este fragmento (la mayor parte del tiempo)
  //interrupts();

//  if (receiving==1) getPulseHexCode();
  
//  delay(500);  
//  receiving=0;
}

// Esta rutina revisa los estados de los puertos de entrada y dispara un evento
// si se detecta un cambio en su valor.
void refreshPortStatus()
{
  int i=0;
  int k=0;
  int val;
  loops++;
  if (loops>=10) loops=0; // Cada 10 loops

  // refresca el estado de los puertos cada X milisegundos
  if(millis() - time < 10)
    return;
  else
    time = millis();


  FOREACHPORT(i) 
  {
    val = 0;
    /*
    if(ISOUTPUT(i))
    {
      if (ISDIGITAL(i)) val=digitalRead(i);
      if(portvalue[i]!=val) triggerPortChange(i,portvalue[i],val);
    }
    else 
    */
    if (ISINPUT(i))
    {
      if (ISDIGITAL(i))
      { 
        // varias lecturas para minimizar problemas de ruido
        val=HIGH;
        for(k=0; k<10; k++)
        {
          if(digitalRead(i)!=HIGH)
          {
            val=LOW;
            break;
          }
          delay(10);
        }  

        
        if(portvalue[i]==LOW && val==HIGH)
        {
          triggerPortChange(i,portvalue[i], HIGH);
          portvalue[i]=HIGH;
        }
        else if (portvalue[i]==HIGH && val==LOW)
        {
          triggerPortChange(i,portvalue[i], LOW);
          portvalue[i]=LOW;
        }
      }
      
      if (ISANALOG(i))
      { 
        val=analogRead(i-DIGICOUNT); // Las entradas analogicas tienen un rango distinto
        triggerPortChange(i,portvalue[i],val);
        portvalue[i]=val;
      }
      

      // Los puertos de entrada son susceptibles a sufrir ruido (verificar solo durante los primeros 5s)
      if (loops==0 && time<5000) 
      {
        if (portchanges[i] >= MAXPORTCHANGESPERSECOND) // puerto ruidoso si hubo mas de X cambios por segundo
        {
          porttype[i]='N'; // Desactivado
          raiseEvent("N",portname[i], "noisy");
        }
        portchanges[i]=0; // Ponemos a cero los contadores
      }  
    }   
    
  }
}

// triggerPortChange: esta funcion es llamada cada vez que se detecta un cambio en uno de los puertos
void triggerPortChange(byte port, byte oldvalue, byte newvalue)
{
  byte i;
  if (oldvalue==newvalue) return;
  
  
  //TODO indicar tolerancias
  //TODO activar valores threshold
  raiseEvent4("N",portname[port], "newval",newvalue);
  portchanges[port]++;

  // Comprobamos rangos máximos y mínimos para los puertos de entrada analógicos
  if (ISINPUT(port) && ISANALOG(port))
  { // Si el valor supera el limite impuesto, y esta subiendo, disparar alarma
    if ((amax[port-DIGICOUNT]<1024) && (newvalue > amax[port-DIGICOUNT]) && (newvalue > oldvalue)) {
      raiseEvent4("A",portname[port],"mxlvl",newvalue);
      setPortVal(alarmport,HIGH);
    }
    if ((wmax[port-DIGICOUNT]<1024) && (newvalue > wmax[port-DIGICOUNT]) && (newvalue > oldvalue)) {
      raiseEvent4("W",portname[port],"mxlvl",newvalue);
    }
    if ((amin[port-DIGICOUNT]>0) && (newvalue < amin[port-DIGICOUNT]) && (newvalue < oldvalue)) {
      raiseEvent4("A",portname[port],"mnlvl",newvalue);
      setPortVal(alarmport,HIGH);
    }
    if ((wmin[port-DIGICOUNT]>0) && (newvalue < wmin[port-DIGICOUNT]) && (newvalue < oldvalue)) {
      raiseEvent4("W",portname[port],"mnlvl",newvalue);
    }
  }

  for (i=0;i<MAXLINKS;i++)
  {
    if (links[i][0]==port) // Puerto enlazado
    {
      raiseEvent("E",portname[links[i][1]],"trgrd");
      switch(links[i][2]) 
      {
        case 'd': // Directo: si A=1, B=1. Si A=0, B=0. Si A=63%, B=63%
          setPortVal(links[i][1],newvalue);
          break;
        case 'i': // Inverso: si A=1, B=0. Si A=0, B=1.
          if (newvalue==LOW) 
            setPortVal(links[i][1],HIGH);
          else
            setPortVal(links[i][1],LOW);
          break;
        case 'p': // Pulso: 0->1 o 1->0 cuando el puerto pasa a HIGH
          if (ISDIGITAL(port) && newvalue==HIGH) 
          {
            if (portvalue[links[i][1]]==LOW)
              setPortVal(links[i][1],HIGH);
            else
              setPortVal(links[i][1],LOW);
          }
          break;
        case 'f': // Fall: 0->1 o 1->0 cuando el puerto pasa a LOW
          if (ISDIGITAL(port) && newvalue==LOW) 
          {
            if (portvalue[links[i][1]]==LOW)
              setPortVal(links[i][1],HIGH);
            else
              setPortVal(links[i][1],LOW);
          }
          break;
          
        default:
          raiseEvent("E","board","invalid link type");
          break;

      }
    }
  }
}
// Asigna el valor indicado al puerto especificado
int setPortVal(byte pnum, byte pval)
{
  if (ISDIGITAL(pnum))
  {
    digitalWrite(pnum,pval);    // Aceptado HIGH y LOW
    portvalue[pnum]=pval;
  }
  else if (ISANALOG(pnum))
  {
    analogWrite(pnum,pval*2.5); // El valor es entre 0 y 255
    portvalue[pnum]=pval;
  }
  else  // Disabled, or invalid type
  {
    return -1;
  }
  return 0;
}

// Muestra un listado de enlaces (ver formato)
void listLinks()
{
  int i;
  int a=0;
  for (i=0;i<MAXLINKS;i++) 
  {
    // Los "slots" vacíos se almacenan como enlace "0-0"
    if ((links[i][0]!=0) && (links[i][1]!=0)) 
    {
      Serial.print("I: l");
      printnum(i);
      Serial.print(" ");
      Serial.print(portname[links[i][0]]);
      Serial.print(" ");
      Serial.print(portname[links[i][1]]);
      Serial.print(" ");
      Serial.println(links[i][2]);        
      a++;
    }
    // Formato de salida:
    // 01234567890123456789012345 (byte nº)
    // I: l0001 port1 port2 type
  }
  if (a==0) raiseEvent("W","board","no links");
}

// Esta rutina muestra un listado de los puertos
void listPorts()
{
  int i;
  int perc;
  FOREACHPORT(i)
  {
    Serial.print("I: ");
    Serial.print(portname[i]);
    if (ISDIGITAL(i))
    {
      Serial.print(" D");
      if (ISINPUT(i))Serial.print("I--- ");
      else Serial.print("O--- ");         
      if (digitalRead(i) == HIGH)
        Serial.print("ON  ");
      else
        Serial.print("OFF ");
      Serial.print(" ");
      printnum(i);
    }
    else if (ISANALOG(i))
    {
      Serial.print(" A");
      if (ISINPUT(i)) 
      {
        Serial.print("I--- ");
        printnum(analogRead(i-DIGICOUNT));
        Serial.print(" ");
        printnum(i);
        Serial.print(" (");
        printnum(amin[i-DIGICOUNT]);
        Serial.print(",");
        printnum(wmin[i-DIGICOUNT]);
        Serial.print(",");
        printnum(wmax[i-DIGICOUNT]);
        Serial.print(",");
        printnum(amax[i-DIGICOUNT]);
        Serial.print(")");
      }
      else 
      {
        Serial.print("O--- ");         
        printnum(portvalue[i]);
        Serial.print(" ");
        printnum(i);
      }
    }
    else if(ISPULSE(i))
    {
      Serial.print(" P---- -    "); 
      printnum(i);
    }
    else
    {
      Serial.print(" X---- -    "); 
      printnum(i);
    }
    Serial.println();
  }    
}

// addLink: Crea un enlace entre dos puertos (entrada->salida)
// Obsérvese que se empezará a rellenar por el final
int addLink(byte port1, byte port2)
{
  int l;
  int slot=-1;
  
  if ((port1==-1) || (port2==-1)) return -2;
  
  if (ISINPUT(port1) && ISOUTPUT(port2))
  {
    for (l=0;l<MAXLINKS;l++)
    {
      if((links[l][0]==port1) && (links[l][1]==port2))
      {
        raiseEvent("E","board", "dupln");
        return -2;
      } 
      else if((links[l][0]==0) && (links[l][1]==0))
      {
        slot = l; // "slot" contiene el ultimo par libre
      }
    }
    if (slot==-1) // No hay slots libres
    {
      raiseEvent("E","board","maxln");
      return -3;
    }
    links[slot][0] = port1;
    links[slot][1] = port2;
    links[slot][2] = 'd'; // Por defecto, tipo "d"
  }
  else
  {
    raiseEvent("E","board","invld");
    return -1;
  }
  return 0;
}

int delLink(byte port1, byte port2)
{
  int l;
  if ((port1==-1) || (port2==-1)) return -2;
  for (l=0;l<MAXLINKS;l++)
  {
    if((links[l][0]==port1) && (links[l][1]==port2))
    {
      links[l][0]=0;
      links[l][1]=0;
      links[l][2]=0;
      return 0;
    } 
  }
  return -1; // Enlace no encontrado
}

// Devuelve el ID del puerto indicado, -1 si no se ha encontrado.
int getPortId(char *label)
{
  int i;
  FOREACHPORT(i)
  {
    if (strcmp6(portname[i],label)) 
    {
      return i;
    }
  }
  raiseEvent("E","board invpt", label);
  return -1;
}

// Esta funcion asigna un valor a un puerto, o devuelve !=0 en caso de error.
int setPortValue(int i, char *arg2)
{
  int j;
//  if(i=-1) return -1;
    
  if (ISINPUT(i))
  {
    raiseEvent("E",portname[i],"roprt");
    return 1;
  }
  
  if (ISDIGITAL(i))
  {
    if (arg2[1]=='n'){
      digitalWrite(i,HIGH);
      portvalue[i]=HIGH;
      raiseEvent("N",portname[i],"ison ");
    }
    else if (arg2[1]=='f')
    {
      digitalWrite(i,LOW);
      portvalue[i]=LOW;
      raiseEvent("N",portname[i],"isoff");
    }
    else 
    {
      raiseEvent("E",portname[i],"invvl");
      return 1;
    }
  } 
  else if (ISANALOG(i))
  {
    j = atoi(arg2);
    analogWrite(i-DIGICOUNT, j);
    raiseEvent4("N",portname[i],"is",j);
  }
  else
  {
    raiseEvent("E",portname[i],"invcf");
    return 1;
  }
}

// Esta funcion procesa activamente el comando recibido
int processInstruction(char *cmd)
{
  int i;
  int j;
  char arg1[6]; // Este argumento contendra el nombre de puerto, 5B + 0
  char arg2[6]; 
  int value=0;
  /* los comandos seguiran siempre la misma estructura:   
   cmd arg01 arg02
   012345678901234 <-byte no.
   cfg portn di
   cfg portn alias
   lnk portn portm
   wmx portn value
   wmn portn value
   set portn value
   */
  GETARG1(cmd,arg1);
  GETARG2(cmd,arg2);

  // Algunos comandos no emplean argumentos: lst eco lln
  if (SPELL3(cmd,'l','s','t'))         // LST
  {
    listPorts();
    return 0;
  }
  else if (SPELL3(cmd,'e','c','h'))    // ECH(o)
  {
    if (echo==0) {raiseEvent("N","board","echo on");  echo=1; } 
    else {raiseEvent("N","board","echo off"); echo=0; }
    return 0;
  }
  else if (SPELL3(cmd,'d','e','f'))    // DEF(ault)
  {
    loadDefaultConfig();
    resetPorts();      
    raiseEvent("N","board","cfdef");
  }
  else if (SPELL3(cmd,'l','o','a'))    // LOA(d)
  {
    if (loadConfig()==0) raiseEvent("N","board","cfloa");
    return 0;
  }
  else if (SPELL3(cmd,'s','a','v'))    // SAV(e)
  {
    if (saveConfig()==0) raiseEvent("N","board","cfsav");
    return 0;
  }
  else if (SPELL3(cmd,'c','f','g'))    // CFG
  {
    i = getPortId(arg1);      
    if(configPort(i,arg2,NULL)==0) raiseEvent("N",portname[i],"cfgok");
    resetPorts();
  }
  else if (SPELL3(cmd,'l','b','l'))    // LBL
  {
    i = getPortId(arg1);      
    if(configPort(i,NULL,arg2)==0) raiseEvent("N",portname[i],"cfgok");
  }
  else if (SPELL3(cmd,'r','e','s'))    // RES(et)
  {  
    // Usaremos solo caracteres imprimibles para mejor trazabilidad
    for (i=0;i<512;i++) EEPROM.write(i,' '); 
    raiseEvent("N","board","memcl");
  }
  else if (SPELL3(cmd,'m','a','p'))    // MAP
  {  
    printMap();
  }
  else if (SPELL3(cmd,'v','e','r'))    // VER(sion)
  { 
    raiseEvent("I","board",VERSION);
  }
  else if (SPELL3(cmd,'n','a','m'))    // NAM(e)
  { 
    printBoardName();
  }
  else if (SPELL3(cmd,'s','n','m'))    // SNM (set name)
  { 
    saveBoardName(arg1);
    raiseEvent("N","board","operation succeeded");
  }
  else if (SPELL3(cmd,'w','m','x'))    // WMX
  {  
    i = getPortId(arg1) - DIGICOUNT;
    wmax[i] = atoi(arg2);
    raiseEvent4("N",arg1,"maxwarning",wmax[i]);
  }
  else if (SPELL3(cmd,'w','m','n'))    // WMN
  {  
    i = getPortId(arg1) - DIGICOUNT;
    wmin[i] = atoi(arg2);
    raiseEvent4("N",arg1,"minwarning",wmin[i]);
  }
  else if (SPELL3(cmd,'a','m','x'))    // AMX
  {  
    i = getPortId(arg1) - DIGICOUNT;
    amax[i] = atoi(arg2);
    raiseEvent4("N",arg1,"maxalarm",amax[i]);
  }
  else if (SPELL3(cmd,'a','m','n'))    // AMN
  {  
    i = getPortId(arg1) - DIGICOUNT;
    amin[i] = atoi(arg2);
    raiseEvent4("N",arg1,"minalarm",amin[i]);
  }
  else if (SPELL3(cmd,'s','e','t'))    // SET
  {
    i = getPortId(arg1);
    setPortValue(i,arg2);
  } 
  else if (SPELL3(cmd,'l','n','k'))    // LNK
  {
    i = getPortId(arg1);
    j = getPortId(arg2);
    if(addLink(i,j)==0) raiseEvent("N",arg1,"linked");
    return 0;
  } 
  else if (SPELL3(cmd,'u','n','l'))    // UNL
  {
    i = getPortId(arg1);
    j = getPortId(arg2);
    if(delLink(i,j)==0) raiseEvent("N",arg1,"unlinked");
    return 0;
  } 
  else if (SPELL3(cmd,'l','l','n'))    // LLN
  {
    listLinks();
    return 0;
  } 
  else if (SPELL3(cmd,'l','c','f'))    // LCF
  {
    i = atoi(arg1);
    if (i<MAXLINKS) 
    {
      raiseEvent4("N","board","configuring link",i);
      links[i][2]=arg2[0]; // Provisional. Por ahora empleamos un solo byte
      return 0;
    }
    else 
    {
       raiseEvent("E","board","invalid link");
       return -1;
    }
  } 
  else
  {   
    raiseEvent("E","board","syntax error");
    
    //TODO Esto es solo para depuracion; eliminar en RC
    Serial.println(cmd);
    Serial.println(" bytes received:");
    for (i=0;i<inspos;i++)
    {
      Serial.print(i,DEC);
      Serial.print(" - ");
      Serial.println(instruction[i],HEX);
    }
    return 1;
  }
}



// Esta funcion es para depuracion. No respeta la sintaxis de ancho fijo de comandos
void printMap()
{
  int i;
  int j;
  int p;
  int b;

  // Usaremos solo caracteres imprimibles para mejor trazabilidad (0 - 509)
  Serial.println();
  Serial.println(" 01234 56789 01234 56789   00010203 04050607 08091011 12131415 16171819");
  Serial.println();
  Serial.print(" ");
  for (i=0;i<25;i++) 
  {
    for (j=0;j<20;j++) 
    {
      p = i * 20 + j; 
      if (p<511) // Abortamos al llegar a 512
      {
        b = EEPROM.read(p);
        if ((b>31) && (b<126))
          Serial.print(b,BYTE);
        else
          Serial.print("?");
        if (((j+1) % 5) == 0)  Serial.print("·"); // Cada 5 Bytes un separador
      }    
    }
    Serial.print("  "); // Separador entre ASCII y HEX

    for (j=0;j<20;j++) 
    {
      p = i * 20 + j; 
      if (p<511) // Abortamos al llegar a 512
      {
        b = EEPROM.read(p);
        if(b>0)
          Serial.print(b,HEX);
        else
          Serial.print("00");
        if (((j+1) % 4) == 0)  Serial.print(" "); // Cada 5 Bytes un separador
      }     
    }
    Serial.println("");
    Serial.print(" ");  
  }
}


